package com.example.demo.util;

import cfca.sadk.algorithm.common.*;
import cfca.sadk.algorithm.util.RSAAndItsCloseSymAlgUtil;
import cfca.sadk.algorithm.util.SM2AndItsCloseSymAlgUtil;
import cfca.sadk.lib.crypto.JCrypto;
import cfca.sadk.lib.crypto.Session;
import cfca.sadk.lib.crypto.jni.JNISoftLib;
import cfca.sadk.org.bouncycastle.asn1.ASN1OctetString;
import cfca.sadk.org.bouncycastle.asn1.ASN1Set;
import cfca.sadk.org.bouncycastle.asn1.DEROctetString;
import cfca.sadk.org.bouncycastle.asn1.cms.*;
import cfca.sadk.org.bouncycastle.asn1.x500.X500Name;
import cfca.sadk.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import cfca.sadk.org.bouncycastle.cms.CMSEnvelopedData;
import cfca.sadk.util.Base64;
import cfca.sadk.util.CertUtil;
import cfca.sadk.util.KeyUtil;
import cfca.sadk.util.Signature;
import cfca.sadk.x509.certificate.X509Cert;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;

public class SM2Utils {
    public static Session session = null;
    static {
        try {
            JCrypto crypto = JCrypto.getInstance();
            crypto.initialize("JSOFT_LIB", null);
            session = crypto.openSession("JSOFT_LIB");
        } catch (PKIException e) {

        }
    }

    public static String encryptByCer(String sourceData, String cer)
            throws Exception {
        byte[] encryptedData =  SM2Utils.envelopeMessage(sourceData,"SM4/ECB/PKCS7Padding",cer);
        return new String(encryptedData);
    }

    public static  byte[] envelopeMessage(String sourceString, String symmetricAlgorithm, String cer) throws Exception{


        byte[] sourceData =sourceString.getBytes("UTF-8");
        X509Cert x509Cert = new X509Cert(cer.getBytes(StandardCharsets.UTF_8));

//            byte[] certBytes = Base64.decode(base64CertString);
//            X509Cert x509Cert = new X509Cert(certBytes);
//
//            byte[] certBtyeRoot =  Base64.decode(base64CertStringRoot);
//            X509Cert x509CertRoot = new X509Cert(certBtyeRoot);
//			byte[] base64Bytes = EnvelopeUtil.envelopeMessage(sourceData, symmetricAlgorithm, new X509Cert[] {x509Cert }, session);
        byte[] base64Bytes = SM2EnvelopeUtil.envelopeMessage(sourceData, symmetricAlgorithm, new X509Cert[] {x509Cert }, session);

        return base64Bytes;
    }

    public static String decrypt(String encryptedData, String sm2Path, String sm2Pwd) throws Exception {
        PrivateKey privateKey = getPrivateKeyFromSM2(sm2Path, sm2Pwd);
        X509Cert cert = CertUtil.getCertFromSM2(sm2Path);
        byte[] sourceData = SM2Utils.openEvelopedMessage(encryptedData, sm2Path, sm2Pwd);
        return new String(sourceData);
    }

    public static  byte[] openEvelopedMessage(String base64EnvelopeMessage, String sm2Path, String sm2Pwd) throws Exception{

        X509Cert certFromSM2 = CertUtil.getCertFromSM2(sm2Path);
        PrivateKey privateKey = getPrivateKeyFromSM2(sm2Path,sm2Pwd);

//            byte[] base64DevBytes = EnvelopeUtil.openEvelopedMessage(base64EnvelopeMessage.getBytes(), privateKey, certFromSM2, session);
        byte[] base64DevBytes = openEvelopedMessage(base64EnvelopeMessage.getBytes("UTF-8"), privateKey, certFromSM2, session);
        return base64DevBytes;
    }

    public static  byte[] openEvelopedMessage(byte[] base64EnvelopeMessage, PrivateKey privateKey, X509Cert recipientCert, Session session) throws PKIException {
        if (session == null) {
            throw new IllegalArgumentException("session");
        } else {
            try {
                boolean isSM2Type = CertUtil.isSM2Cert(recipientCert);
                byte[] bEnvelop = Base64.decode(base64EnvelopeMessage);
                CMSEnvelopedData cmsEnData = new CMSEnvelopedData(bEnvelop);
                ContentInfo info = cmsEnData.toASN1Structure();
                EnvelopedData enData = EnvelopedData.getInstance(info.getContent());
                ASN1Set receivers = enData.getRecipientInfos();
                X500Name recipientIssuer = recipientCert.getIssuerX500Name();
                BigInteger recipientSN = recipientCert.getSerialNumber();
                byte[] subjectPubKeyID = recipientCert.getSubjectKeyIdentifier().getKeyIdentifier();
                if (receivers == null) {
                    throw new PKIException("the receiver is null!!!");
                } else {
                    ASN1OctetString encryptKey = null;
                    AlgorithmIdentifier algId = null;
                    int len = receivers.size();

                    for(int i = 0; i < len; ++i) {
                        RecipientInfo recip = RecipientInfo.getInstance(receivers.getObjectAt(i));
                        if (recip.getInfo() instanceof KeyTransRecipientInfo) {
                            KeyTransRecipientInfo inf = KeyTransRecipientInfo.getInstance(recip.getInfo());
                            if (hasRecipent(inf, subjectPubKeyID, recipientIssuer, recipientSN)) {
                                encryptKey = inf.getEncryptedKey();
                                algId = inf.getKeyEncryptionAlgorithm();
                                break;
                            }
                        }
                    }

                    if (encryptKey != null && algId != null) {
                        Mechanism contentEncryptionAlg = null;
                        if (isSM2Type) {
                            contentEncryptionAlg = new Mechanism("SM2");
                        } else {
                            contentEncryptionAlg = new Mechanism("RSA/ECB/PKCS1PADDING");
                        }

                        byte[] symmetricKey = session.decrypt(contentEncryptionAlg, privateKey, encryptKey.getOctets());
                        EncryptedContentInfo data = enData.getEncryptedContentInfo();
                        ASN1OctetString os = data.getEncryptedContent();
                        AlgorithmIdentifier symmetricAlgId = data.getContentEncryptionAlgorithm();
                        String encryptionAlgStr = (String)PKCS7EnvelopedData.OID_MECH.get(symmetricAlgId.getAlgorithm());
//                        String encryptionAlgStr = "SM4/ECB/PKCS7Padding";


                        Mechanism mechanism = null;
                        CBCParam sourceData;
                        if (encryptionAlgStr.indexOf("CBC") != -1) {
                            DEROctetString doct = (DEROctetString)symmetricAlgId.getParameters();
                            sourceData = new CBCParam(doct.getOctets());
                            if (encryptionAlgStr.equals("DESede/CBC/PKCS7Padding")) {
                                mechanism = new Mechanism("DESede/CBC/PKCS7Padding", sourceData);
                            } else if (encryptionAlgStr.equals("SM4/CBC/PKCS7Padding")) {
                                mechanism = new Mechanism("SM4/CBC/PKCS7Padding", sourceData);
                            }
                        } else if (encryptionAlgStr.indexOf("ECB") != -1) {
                            if (encryptionAlgStr.equals("DESede/ECB/PKCS7Padding")) {
                                mechanism = new Mechanism("DESede/ECB/PKCS7Padding");
                            } else if (encryptionAlgStr.equals("SM4/ECB/PKCS7Padding")) {
                                mechanism = new Mechanism("SM4/ECB/PKCS7Padding");
                            }
                        } else if (encryptionAlgStr.indexOf("RC4") != -1) {
                            mechanism = new Mechanism("RC4");
                        }

                        if (mechanism == null) {
                            throw new PKIException(PKIException.UNSUPPORT_ENCRYPT_ALG_SIGNANDENVELOP_ERR, PKIException.UNSUPPORT_ENCRYPT_ALG_SIGNANDENVELOP_ERR_DES + "Algorithm is:" + encryptionAlgStr);
                        } else {
                            boolean useJNI = false;
                            if (session != null && session instanceof JNISoftLib) {
                                useJNI = true;
                            }

                            sourceData = null;
                            byte[] sourceDatas ;
                            if (isSM2Type) {
                                sourceDatas = SM2AndItsCloseSymAlgUtil.crypto(useJNI, false, symmetricKey, os.getOctets(), mechanism);
                            } else {
                                sourceDatas = RSAAndItsCloseSymAlgUtil.crypto(useJNI, false, symmetricKey, os.getOctets(), mechanism);
                            }

                            return sourceDatas;
                        }
                    } else {
                        throw new PKIException("can not find the receiver!!!");
                    }
                }
            } catch (Exception var25) {
                throw new PKIException("850935", "解析消息数字信封失败", var25);
            }
        }
    }
    private static boolean hasRecipent(KeyTransRecipientInfo inf, byte[] subjectPubKeyID, X500Name recipientIssuer, BigInteger recipientSN) {
        RecipientIdentifier id = inf.getRecipientIdentifier();
        DEROctetString oct = new DEROctetString(subjectPubKeyID);
        IssuerAndSerialNumber issu = new IssuerAndSerialNumber(recipientIssuer, recipientSN);
        return id.getId().toASN1Primitive().asn1Equals(oct) || id.getId().toASN1Primitive().asn1Equals(issu.toASN1Primitive());
    }
    public static PrivateKey getPrivateKeyFromSM2(String sm2Path, String sm2Pwd) throws Exception {
        return KeyUtil.getPrivateKeyFromSM2(sm2Path, sm2Pwd);
    }


    public static  String P7SignMessageDetach(String signAlg, String base64SourceString, String sm2Path, String sm2Pwd) {
        try {

            byte[] sourceData =  Base64.decode(base64SourceString);
            PrivateKey privateKey = getPrivateKeyFromSM2(sm2Path,sm2Pwd);
            X509Cert certFromSM2 = CertUtil.getCertFromSM2(sm2Path);
            Signature signature = new Signature();
            byte[] base64Bytes = signature.p7SignMessageDetach(signAlg, sourceData, privateKey, certFromSM2, session);
            return new String(base64Bytes);
        } catch (Exception e) {
            return "";
        }
    }

    public static  boolean P7VerifyMessageDetach(String base64SourceString, String base64SignatureString,String publickey) {
        try {

            byte[] sourceData = Base64.decode(base64SourceString);
            byte[] signatureData = Base64.decode(base64SignatureString);
            Signature signature = new Signature();
            PKCS7SignedData p7 = new PKCS7SignedData(session);
            p7.loadBase64(signatureData);
            X509Cert signerX509Cert = p7.getSignerX509Cert();
            X509Cert clientCer = new X509Cert(publickey.getBytes(StandardCharsets.UTF_8));
            BigInteger serialNumber = signerX509Cert.getSerialNumber();
            String subject = signerX509Cert.getSubject();
//            System.out.println("subjectOfPkCS7 :"+subject);
            BigInteger serialNumberOfclient = clientCer.getSerialNumber();

            String subjectOfclient = clientCer.getSubject();
//            System.out.println("subjectOfclient:"+subjectOfclient);

            if (serialNumber.compareTo(serialNumberOfclient)!=0){
               System.out.println("序列号["+serialNumber+"]与商户上传证书序列号["+serialNumberOfclient+"]不符");
                return false;
            }
            if (!subject.equals(subjectOfclient)){
                System.out.println("证书DN["+subject+"]与商户上传证书序列号DN["+subjectOfclient+"]不一致{}");
                return false;
            }
            boolean result = signature.p7VerifyMessageDetach(sourceData, signatureData, session);
            return result;
        } catch (Exception e) {
            return false;
        }
    }
	public static void main(String[] args) throws Exception {

        //签名
        String signdata = "测试一下sign";
        String merSignPath = "D:\\dev_docs\\interface_docs\\多级账户\\cms-demo\\src\\resources\\cert\\mechant_sign_sm2.sm2";
        String mac = SM2Utils.P7SignMessageDetach("sm3WithSM2Encryption", new String(Base64.encode(signdata.getBytes("UTF-8"))), merSignPath, "UMBPAY2022");
        System.out.println("软算法签名结果:" + mac);
        //加密
        String encdata = "测试一下enc";
        String content = new String(Files.readAllBytes(Paths.get("D:\\dev_docs\\interface_docs\\多级账户\\cms-demo\\src\\resources\\cert\\mechant_enc_sm2.cer")));
        content = content.replaceAll("\\n", "");
        content = content.replace("\\r", "");
        String encryptdata = SM2Utils.encryptByCer(encdata, content);
        System.out.println("软算法加密结果:" + encryptdata);

        //解密
//        String encodeData = "MIIBUwYKKoEcz1UGAQQCA6CCAUMwggE/AgEAMYH9MIH6AgEAMGowYTELMAkGA1UEBhMCQ04xMDAuBgNVBAoMJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEgMB4GA1UEAwwXQ0ZDQSBBQ1MgVEVTVCBTTTIgT0NBMzECBUA5mEQ4MA0GCSqBHM9VAYItAwUABHoweAIgcSJGB7a+XvMp01dUh0jDkvSLqL9Ryg1B4lRPAtf0yjkCIFd7EO9Crkct0l1rXpX617QfqIJTNH4ynZJ99KTomQPjBCBlizJvIf2sc0LC+UEGRNngwgB0uls7E8X5zR+Fo+NEagQQ8ClBofyQehT8J0gBou75GzA6BgoqgRzPVQYBBAIBMAoGCCqBHM9VAWgBgCCy8bGp/kCZ693AhMBiq9WwlYo9BQRcTX6dpIElKi3iJg==";
        byte[] decodeData = SM2Utils.openEvelopedMessage(encryptdata, "D:\\dev_docs\\interface_docs\\多级账户\\cms-demo\\src\\resources\\cert\\mechant_enc_sm2.sm2", "UMBPAY2022");
        System.out.println("解密结果:" + new String(decodeData,"UTF-8"));
        //验签
//        String signencdata = "MIICdAYKKoEcz1UGAQQCAqCCAmQwggJgAgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIIBfjCCAXowggEdoAMCAQICCH0bmmUy5wEqMAwGCCqBHM9VAYN1BQAwQTEQMA4GA1UEAwwHU00yUk9PVDEPMA0GA1UECgwGU0FOU0VDMQ8wDQYDVQQLDAZTQU5TRUMxCzAJBgNVBAYTAkNOMB4XDTIwMDcwNjA2MDI0OFoXDTMwMDcwNjA2MDI0OFowQDEPMA0GA1UEAwwGc20yMDAxMQ8wDQYDVQQKDAZTQU5TRUMxDzANBgNVBAsMBlNBTlNFQzELMAkGA1UEBhMCQ04wWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAAS/6jVkhsWytKVdmtflqX9LhkU+dLApthBO1Y28PTwVmLW1ph1ggT9qjmYxxc/h2bRyuryPz7wmlOBlSlbV4aM3MAwGCCqBHM9VAYN1BQADSQAwRgIhAK5cTEqSgB/aNAljGFstp7ze9nw5toOPSfBNtaktBoh6AiEA9vJr34EaGRpVUg+7INzxo2YX2ieYmJOmuL5tGSaHgEwxgbowgbcCAQEwTTBBMRAwDgYDVQQDDAdTTTJST09UMQ8wDQYDVQQKDAZTQU5TRUMxDzANBgNVBAsMBlNBTlNFQzELMAkGA1UEBhMCQ04CCH0bmmUy5wEqMAwGCCqBHM9VAYMRBQAwDQYJKoEcz1UBgi0BBQAERjBEAiAMHxbWRr5gVdfjjINBB9/SkWKeozAEXpSXO7bKSp+n9QIgIH0wgPysxQwvyZWuS/AiAdS+gmqhqsoOqu7O17nzMao=";
        boolean b1 = SM2Utils.P7VerifyMessageDetach(new String(Base64.encode("测试一下sign".getBytes("UTF-8"))), mac,content);
        System.out.println("验签结果" + b1);
    }
}
